package com.aos;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Semaphore;


public class Node 
{
	public static int port;
	public static int dNode; //Distinguished node
	public static ArrayList<Integer> neighborIDList;
	public static NodeInfo nodeInfo;
	public static HashMap<Integer, NodeInfo> neighbors;
	public static int totalNodes;
	public static int readyCount; //to help determining the node is ready
	private static String configFilePath;
	
	public static int minDegree = 1;
	public static int maxDegree = 1;
	public static String spanningTree = "";
	public static String specificDegreeNodes = "";
	public static boolean spanTerminated = false;
	public static int tMsgCount = 0;
	public static int childCount = 0;
	
	public static int hRplMsgCount = 0;
	public static int maxHeight = -1;
	public static Queue<Integer> tokenReqQueue = new LinkedList<Integer>();
	public static boolean isHeightComputed = false;
	public static String heightInfo = "";
	
	public static int specifiedDegree;
	
	ArrayList<NodeInfo> nodelist = new ArrayList<NodeInfo>();
	public static Semaphore hostSemaphore = new Semaphore(100, true);
	
	
	//***********Mustafa************
	public static int parent = -1;  // node's parent ID
	public static ArrayList<Integer> children = new ArrayList<Integer>(); // List of node's children
	public static ArrayList<Integer> others = new ArrayList<Integer>(); // List of nodes other than children
	//*********************************
	
	public Node(int port, int specifiedDegree)
	{
		Node.port = port;		
		Node.specifiedDegree = specifiedDegree;
		
		ConfigReader.readConfiguration();
		
		Node.tokenReqQueue.add(Node.nodeInfo.getID());	//initialize token request queue with self node
		
		Server server = new Server();
		Client client = new Client();
		SpanTreeDeamon spanDeamon = new SpanTreeDeamon();
		
		//System.out.println("Strting Server");
		Thread serverThread = new Thread(server);
		serverThread.start();
		
		//System.out.println("Starting Client");
		Thread clientThread = new Thread(client);
		clientThread.start();
		
		Thread spanDeamonThread = new Thread(spanDeamon);
		spanDeamonThread.start();
		
		while(true)
		{
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) 
			{
				e.printStackTrace();
			}
		}
	}
	
	public static synchronized boolean isNodeUp()
	{
		boolean isNodeUp = false;
		try {
			hostSemaphore.acquire();
			//check to see whether both server and client are up
			if(readyCount == 2)
			{
				isNodeUp = true;
			}
			hostSemaphore.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}		
		return isNodeUp;
	}
	
	//checking whether spanning process has completed and all t messages has been received
	public static synchronized boolean isSpanningCompleted()
	{
		boolean completed = false;
		try {
			hostSemaphore.acquire();
			if(spanTerminated & (tMsgCount == Node.childCount))
			{
				completed = true;
			}
			hostSemaphore.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return completed;
	}
	
	public static synchronized void incTMsgCount()
	{
		try {
			hostSemaphore.acquire();
			tMsgCount++;
			hostSemaphore.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public static synchronized void incChildCount()
	{
		try {
			hostSemaphore.acquire();
			childCount++;
			hostSemaphore.release();
		} catch (InterruptedException e) {

			e.printStackTrace();
		}		
	}
	public static synchronized void updateNodeState()
	{
		try {
			hostSemaphore.acquire();
			//increment count as a result of either server up or client up
			readyCount++;
			if(readyCount == 2)
			{
				//System.out.println("Node is Up.");
			}
			hostSemaphore.release();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
		}
	}
	
	//update when spanning process terminates
	public static synchronized void terminateSpanningProcess()
	{
		try {
			hostSemaphore.acquire();
			spanTerminated = true;
			hostSemaphore.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}		
	}
	public static synchronized boolean hasReceivedAllHRplMsg()
	{
		boolean flag = false;
		try {
			hostSemaphore.acquire();
			if(hRplMsgCount == Node.children.size())
			{
				hRplMsgCount = 0;
				flag = true;
			}
			hostSemaphore.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return flag;
	}
	public static synchronized void incHRplMsgCount()
	{
		try 
		{
			hostSemaphore.acquire();
			hRplMsgCount++;
			hostSemaphore.release();
		} catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
		
	}
	public static synchronized void updateMaxHeight(int height)
	{
		try 
		{
			hostSemaphore.acquire();
			maxHeight = Math.max(maxHeight, height);
			hostSemaphore.release();
		} catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
		
	}
	public static synchronized void resetMaxHeight()
	{
		try {
			hostSemaphore.acquire();
			maxHeight = -1;
			hostSemaphore.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}		
	}
	public static synchronized void updateSpanTreeInfo(int minDegree, int maxDegree, String spanTree, String specificDegreeNodes)
	{
		try 
		{
			hostSemaphore.acquire();
			Node.minDegree = Math.min(Node.minDegree, minDegree);
			Node.maxDegree = Math.max(Node.maxDegree, maxDegree);
			Node.spanningTree = spanTree + " " + Node.spanningTree;
			Node.specificDegreeNodes = specificDegreeNodes + " " + Node.specificDegreeNodes;
			hostSemaphore.release();
		} catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
	}
	
	public static void printOutputFile()
	{
		HashMap<Integer, Integer> heightMap = new HashMap<Integer, Integer>();
		try {
			BufferedWriter br = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File("output.txt"))));
			br.write("# Spanning Tree\n");
			br.write(Node.spanningTree+"\n");
			br.write("# Minimum degree\n");
			br.write(Node.minDegree + "\n");
			br.write("# Maximum degree\n");
			br.write(Node.maxDegree + "\n");
			br.write("# Root Node,Height of the spanning tree\n");
			String[] pairs = Node.heightInfo.split(",");
			
			int minNode = -1;
			int minHeight = 99999;
			
			for(String pair : pairs)
			{
				if(!pair.trim().isEmpty())
				{
					String[] str = pair.split(" ");
					int node = Integer.parseInt(str[0]);
					int height = Integer.parseInt(str[1]);
					br.write(node + "," + height + "\n");
					if(height < minHeight)
					{
						minHeight = height;
						minNode = node;
					}
				}
			}
			br.write("Node with minimum height: " + minNode + "\n");
			br.close();
			
		} catch (Exception e) {	
			e.printStackTrace();
		}
		System.out.println("Finish!!!");
	}
	
	public static void main(String[] args)
	{
		new Node(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
	}		
}
